﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using System.Web.Script.Serialization;
using Fuse8.ContentManagementFramework.SystemFacade.Extensions;
using Fuse8.DomainFramework.Common;
using Fuse8.DomainFramework.Common.Locator;
using Fuse8.DomainFramework.Domain;
using Fuse8.DomainFramework.BusinessService;

using Fuse8.ContentManagementFramework.Domain.Special;
using Fuse8.ContentManagementFramework.Domain.Commands;
using Fuse8.ContentManagementFramework.Domain.Commands.CommandParameters;
using Fuse8.ContentManagementFramework.SystemFacade.Utilities;

namespace Fuse8.ContentManagementFramework.Domain
{
    /// <summary>
    /// Provides methods for working with MetaField model
    /// </summary>
    [Serializable]    
    public abstract class CmsComplexEntityObject : Int32EntityObject
    {
        #region Fields

        [NonSerialized]
        private CmsEntityMetaInformation _entityMetaInformation;
               
        private bool _isMetaInformationLoaded;
        
        #endregion

        #region Properties

        public override int Id
        {
            get
            {
                return base.Id;
            }
            set
            {
                if (base.Id != value)
                {
                    _entityMetaInformation = null;
                    _isMetaInformationLoaded = false;
                }
                base.Id = value;
            }
        }

        public virtual bool UseBuiltInFlow
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Indicates whether meta field information for this entity is loaded
        /// </summary>
        public bool IsMetaInformationLoaded
        {
            get
            {
                return _isMetaInformationLoaded;
            }
        }

        /// <summary>
        /// Gets or sets Meta fields information for this entity
        /// </summary>
        /// <remarks>Get accessor executes <see cref="CmsEntityMetaGetInformationCommand"/> command if entity meta information was not set before</remarks>
        [XmlIgnore]
        [ScriptIgnore]
        public virtual CmsEntityMetaInformation EntityMetaInformation
        {
            get
            {
                if (_entityMetaInformation == null)
                {
                    var command = DependencyServiceLocator.Current.GetInstance<CmsEntityMetaGetInformationCommand>();

                    var commandParameter = new CmsEntityMetaGetInformationCommandParameter()
                    {
                        EntityId = IsCreated ? Id : new Nullable<int>(),
                        EntityTypeKey = TypeParameter
                    };

                    command.ConfigureCommand(commandParameter);

                    _entityMetaInformation = new BusinessServiceFacade<CmsEntityMetaInformation, int>().FindOne(command);
                    _isMetaInformationLoaded = true;
                }

                return _entityMetaInformation ?? new CmsEntityMetaInformation();
            }
            set
            {
                _entityMetaInformation = value;
                _isMetaInformationLoaded = true;
            }
        }

        /// <summary>
        /// Gets or sets list of all meta fields with  values for this entity
        /// </summary>
        [XmlIgnore]
        [ScriptIgnore]
        public IList<CmsEntityMeta> Metas
        {
            get
            {
                return EntityMetaInformation != null ?
                    EntityMetaInformation.AllMetas.ToList() :
                    new List<CmsEntityMeta>();
            }
            set
            {
                SetMetas(value);
            }
        }

        #endregion

        #region Methods
        
        /// <summary>
        /// Gets typed value from meta field by specified name
        /// </summary>
        /// <typeparam name="T">Type of value</typeparam>
        /// <param name="metaFieldName">Meta field name</param>
        /// <returns>An instance of <see cref="T"/> or default of <see cref="T"/></returns>
        public T GetMetaValue<T>(string metaFieldName)
        {
            return GetMetaValues<T>(metaFieldName).FirstOrDefault();
        }

        /// <summary>
        /// Gets all typed values from meta field by specified name
        /// </summary>
        /// <typeparam name="T">Type of each value in list</typeparam>
        /// <param name="metaFieldName">Meta field name</param>
        /// <returns>Returns collection of typed values</returns>
        public IEnumerable<T> GetMetaValues<T>(string metaFieldName)
        {
            var result = new List<T>();

            IEnumerable<CmsEntityMeta> metas = EntityMetaInformation.AllMetas;
            if (metas != null)
            {
                metas = metas.Where(p => p.MetaFieldName == metaFieldName);

                foreach (var item in metas)
                {
                    if (item != null)
                    {
                        var objectResult = TypeInterpreter.GetValue(item.MetaFieldType, item.EntityMetaValue, EntitiesConverter.ConvertToType);
                        if (objectResult != null)
                        {
                            result.Add((T)objectResult);
                        }
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Sets value for meta field by specified name
        /// </summary>
        /// <param name="metaFieldName">Meta field name</param>
        /// <param name="metaValue">Value to set</param>
        /// <param name="metaType">Type of value to set</param>
        /// <param name="immediatelyCreate">Indicates that applying changes should be execute immediately. If false, it is needed to save all meta field separately</param>
        /// <remarks>If there is no <see cref="CmsEntityMetaField"/> in data storage it will be created</remarks>
        public void SetMetaValue(string metaFieldName, object metaValue, Type metaType, bool immediatelyCreate = false)
        {
            CmsEntityMeta meta = EntityMetaInformation.Metas != null
                                     ? EntityMetaInformation.Metas.FirstOrDefault(p => p.MetaFieldName == metaFieldName)
                                     : null;

            if (meta != null)
            {
                meta.EntityMetaValue = metaValue != null ? ConvertMetaValueToString(metaValue, metaType) : "";

                if (immediatelyCreate)
                {
                    var cmsEntityMetaService = new BusinessServiceFacade<CmsEntityMeta, int>();

                    cmsEntityMetaService.Save(meta);
                }
            }
            else
            {
                CmsMetaFieldGetByNameAndEntityTypeCommand getCommand = DependencyServiceLocator.Current.GetInstance<CmsMetaFieldGetByNameAndEntityTypeCommand>();

                Type entityType = metaValue != null ? metaValue.GetType() : metaType;

                var commandParameter = new CmsMetaFieldGetByNameCommandParameter()
                {
                    MetaFieldName = metaFieldName,
                    MetaFieldType = TypeInterpreter.GetTypeName(entityType),
                    EntityType = TypeParameter
                };

                getCommand.ConfigureCommand(commandParameter);

                var cmsEntityMetaFieldService = new BusinessServiceFacade<CmsEntityMetaField, int>();

                var entityMetaField = cmsEntityMetaFieldService.FindOne(getCommand);

                if (entityMetaField == null)
                {
                    CmsEntityPropertyCreateCommand createCommmand = DependencyServiceLocator.Current.GetInstance<CmsEntityPropertyCreateCommand>();

                    createCommmand.ConfigureCommand(
                        new CmsEntityPropertyCreateCommandParameter
                        {
                            MetaFieldName = metaFieldName,
                            MetaFieldType = TypeInterpreter.GetTypeName(entityType),
                            EntityType = TypeParameter
                        });

                    using (var transaction = new BusinessServiceTransaction<CmsEntityMetaField>())
                    {
                        entityMetaField = cmsEntityMetaFieldService.FindOne(createCommmand);

                        transaction.Commit();
                    }
                }

                var result = new CmsEntityMeta
                {
                    EntityMetaFieldLinkId = entityMetaField.EntityMetaFieldLinkId,
                    MetaFieldName = entityMetaField.MetaFieldName,
                    EntityId = Id,
                    EntityMetaValue = metaValue != null ? ConvertMetaValueToString(metaValue, metaType) : ""
                };

                if (immediatelyCreate)
                {
                    var cmsEntityMetaService = new BusinessServiceFacade<CmsEntityMeta, int>();

                    cmsEntityMetaService.Add(result);
                }

                EntityMetaInformation.Metas.Add(result);
            }
        }

        private static string ConvertMetaValueToString(object metaValue, Type type)
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
            {
                return metaValue.StoreMe(type);
            }
            return metaValue.ToString();
        }

        /// <summary>
        /// Sets value for meta field by specified name
        /// </summary>
        /// <typeparam name="T">Type of value to set</typeparam>
        /// <param name="metaFieldName">Meta field name</param>
        /// <param name="metaValue">Value to set</param>
        /// <param name="immediatelyCreate">Indicates that applying changes should be execute immediately. If false, it is needed to save all meta field separately</param>
        /// <remarks>If there is no <see cref="CmsEntityMetaField"/> in data storage it will be created</remarks>
        public void SetMetaValue<T>(string metaFieldName, T metaValue, bool immediatelyCreate = false)
        {
            SetMetaValue(metaFieldName, metaValue, typeof(T), immediatelyCreate);
        }

        private void SetMetas(IList<CmsEntityMeta> listMetas)
        {
            if ((listMetas != null) && (listMetas.Count > 0))
            {
                string key = string.Empty;

                var entityMetas = EntityMetaInformation.Metas;

                foreach (var listItem in listMetas)
                {
                    if (listItem != null)
                    {
                        var meta = entityMetas.FirstOrDefault(p => p.MetaFieldName == listItem.MetaFieldName);

                        if (meta != null)
                        {
                            entityMetas[entityMetas.IndexOf(meta)] = listItem;
                        }
                        else
                        {
                            entityMetas.Add(listItem);
                        }
                    }
                }
            }
        }

        #endregion
    }
}
